package qc.module.iot.protocol.sl651;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qc.common.core.constants.IotConstant;
import qc.common.core.enums.iot.IotPacketsDataFrameTypeEnum;
import qc.common.core.enums.iot.IotPacketsDataProtocolEnum;
import qc.common.core.utils.CrcUtil;
import qc.common.core.utils.RadixConvertUtil;
import qc.module.iot.protocol.IotFrameBodyDataItem;
import qc.module.iot.protocol.IotFrameBodyDataItemTypeEnum;
import qc.module.iot.protocol.IotFrameParseBase;
import qc.module.iot.protocol.IotFrameParseResult;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * SL651_Ascii数据帧解析类，提供SL651_Ascii协议的报文分解、数据帧解析、回复报文生成方法
 *
 * @author QuCheng Tech
 * @since 2025/11/5
 */
public class SL651_Ascii_FrameParse extends IotFrameParseBase {
    private static final Logger logger = LoggerFactory.getLogger(SL651_Ascii_FrameParse.class);

    //SL651 Ascii协议解析参考：https://blog.csdn.net/weixin_42163707/article/details/132556389

    /**
     * 数据帧报文协议
     */
    private static final IotPacketsDataProtocolEnum protocol = IotPacketsDataProtocolEnum.SL651_ASCII;

    /**
     * 解析数据帧错误提示信息前缀
     */
    private static final String parseErrorMessagePrefix = "使用SL651 ASCII协议解析数据帧时错误，";

    /**
     * 数据帧最小长度，包含起始符和结束符的最小有效长度
     */
    private static final int Frame_Length_Min_Total = 29;

    //每种数据协议中使用静态定义协议中的数据帧起始符、结束符等，通过构造函数传入到父类中
    /**
     * 数据帧起始符字节
     */
    private static final byte Frame_Start_Byte = SL651_Constant.SOH_ASCII;
    /**
     * 报文起始符
     */
    private static final byte Packets_Start_Byte = SL651_Constant.STX;
    /**
     * 报文结束符
     */
    private static final byte Packets_End_Byte = SL651_Constant.ETX;
    /**
     * 内容中的分隔符，0x20 空格
     */
    private static final byte Content_Split_Byte = 0x20;

    /**
     * 监测参数无数据或未采集该参数值时的所有字节取值，使用大写字母M表示
     */
    private static final byte NO_VALUE_Byte = (byte) 0x4D;//M对应0x4D

    public SL651_Ascii_FrameParse() {
        //通过调用父类的构造函数传入协议中的固定内容
        super(protocol, new byte[]{Frame_Start_Byte}, null, Frame_Length_Min_Total);
        //也可以在调用构造函数后显式对变量或调用方法进行赋值
    }

    //SL651 Ascii协议只根据起始字符和结束字符进行判断，可以直接使用父类中提供的通用方法（不要重写父类方法），或者在重写父类方法中调用父类提供的方法
    //@Override
    //public List<IotPacketsSplitResult> split(byte[] packetsBytes) {
    //    logger.debug("split");
    //    
    //    //return super.split(bytes);
    //
    //    return null;
    //}

    /**
     * 解析数据帧
     *
     * @param inputBytes    数据帧报文字节数组，数组起始必须时数据值对应协议的起始符
     * @param parseBodyData 是否解析报文中的正文内容数据，为false时表示只解析数据帧基本结构框架，为true时表示解析解析结构框架和正文内容数据
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/5
     */
    @Override
    public IotFrameParseResult parse(byte[] inputBytes, boolean parseBodyData) {
        logger.debug("parseBaseStruct");

        //初始化解析返回结果，设置解析成功标记为false
        IotFrameParseResult result = new IotFrameParseResult();
        result.setParseSuccess(false);
        result.setProtocol(protocol);
        result.setDataTime(null);//初始化数据时标为null
        //输入的字节数组赋值，根据数据协议确定是否要对Hex字符串和Ascii字符串进行赋值
        result.setInputBytes(inputBytes);
        result.setInputHexString(RadixConvertUtil.toHexString(inputBytes));

        /* SL651 Ascii 上行报文数据帧
         * 结构框架：帧起始符 中心站地址 遥测站地址 密码 功能码 上下行标记及长度 报文起始符 报文正文 报文结束符 校验码
         * 长度字节：   1      2       10      4    2        4           1        n        1     4
         * 固定内容： 帧起始符-01、报文起始符-02、报文结束符-03
         * 解析举例：01 3031 30303030303030303133 39393939 3333 30303432 02 3033423731383036323231363335303053542030303030303030303133204B2054542031383036323231363335205A55203836372E3338302056542031342E323520 03 37313441
         * 解析举例：01 3031 30303030303030303031 30343531 3333 30303434 02 3036383332353131313031303430313253542030303030303030303031204B2054542032353131313031303430205A3120312E363530205A204D2056542031312E343520 03 32303944
         */
        if (inputBytes == null || inputBytes.length < this.frameMinLength)
            return returnParseError(result, "报文内容为空");
        //记录当前解析判断到的位置索引，数据帧的每个部分在解析前均要判断输入的字节数组总长是否足够
        int index = 0x0;
        //判断有效数据帧报文最小总长度
        int frameMinTotalLength = 0x0;
        //数据帧的每个结构框架部分的长度，判断到每个部分长度时进行赋值
        int framePartLength = 0x0;

        //帧起始符，长度为1字节
        //framePartLength=this.frameStartBytes.length;//可以从静态定义中读取长度，也可以直接固定长度
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的帧起始符时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        for (int i = 0; i < framePartLength; i++) {
            if (inputBytes[index + i] != this.frameStartBytes[i])
                return returnParseError(result, "帧起始符错误");
        }
        index += framePartLength;//帧起始符判断通过，索引加上帧起始符长度

        //中心站地址，长度为2字节
        framePartLength = 0x2;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的心站地址时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] centerAddressBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setCenterAddressBytes(centerAddressBytes);
        result.setCenterAddress(RadixConvertUtil.toAsciiString(centerAddressBytes));
        index += framePartLength;//帧中心站地址判断通过，索引加上帧中心站地址长度

        //遥测站地址，长度为10字节
        framePartLength = 0xA;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的遥测站地址时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] stationAddressBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setStationAddressBytes(stationAddressBytes);
        result.setStationAddress(RadixConvertUtil.toAsciiString(stationAddressBytes));
        index += framePartLength;//帧遥测站地址判断通过，索引加上帧遥测站地址长度

        //密码，长度为4字节
        framePartLength = 0x4;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的密码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] passwordBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setPasswordBytes(passwordBytes);
        result.setPassword(RadixConvertUtil.toAsciiString(passwordBytes));
        index += framePartLength;//帧密码判断通过，索引加上帧密码长度

        //功能码，长度为2字节
        framePartLength = 0x2;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的功能码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] functionCodeBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setFunctionCodeBytes(functionCodeBytes);
        //byte数组转换为hex，再通过hex转换为对应的十进制数值        
        String functionCodeHexString = RadixConvertUtil.toAsciiString(functionCodeBytes);
        int functionCodeIntValue = RadixConvertUtil.hexStringToIntValue(functionCodeHexString);
        if (functionCodeIntValue < 0x0)
            return returnParseError(result, "数据帧中的功能码错误，功能码报文为[" + RadixConvertUtil.toHexString(functionCodeBytes) + "]，Hex为[" + functionCodeHexString + "]");
        result.setFunctionCode(functionCodeHexString);
        index += framePartLength;//帧功能码判断通过，索引加上帧功能码长度
        //功能码解析，根据功能码对数据值类型进行赋值，确定是心跳还是数据帧
        SL651_FrameFunctionCodeEnum functionCodeEnum = SL651_FrameFunctionCodeEnum.getSL651FrameFunctionCodeEnum(functionCodeIntValue);
        result.setFrameName(functionCodeEnum.getName());
        IotPacketsDataFrameTypeEnum frameType = IotPacketsDataFrameTypeEnum.UN_DEFINE;
        if (functionCodeEnum == SL651_FrameFunctionCodeEnum.HEART_BEAT)
            frameType = IotPacketsDataFrameTypeEnum.HEART_BEAT;
        else if (functionCodeEnum.getIndex() >= SL651_FrameFunctionCodeEnum.REPORT_TEST.getIndex() &&
                functionCodeEnum.getIndex() <= SL651_FrameFunctionCodeEnum.REPORT_MANUAL.getIndex())
            frameType = IotPacketsDataFrameTypeEnum.DATA;
        result.setFrameType(frameType);

        //上下行标记及长度，长度为4字节
        framePartLength = 0x4;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的上下行标记及长度时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        //第1个字节中的高4位用作上下行标识，需要取hex字符串中4个字符的前2个字符（对应长度为2字节），一次取出4个hex字符串，
        byte[] directionFlagBytes = Arrays.copyOfRange(inputBytes, index, index + 0x2);
        byte[] directionFlagAndLengthBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        //byte数组转换为hex，再通过hex转换为对应的十进制数值
        String directionFlagAndLengthHexString = RadixConvertUtil.toAsciiString(directionFlagAndLengthBytes);
        int directionFlagAndLengthIntValue = RadixConvertUtil.hexStringToIntValue(directionFlagAndLengthHexString);
        if (directionFlagAndLengthIntValue < 0x0)
            return returnParseError(result, "数据帧中的上下行标记错误，上下行标记报文为[" + RadixConvertUtil.toHexString(directionFlagAndLengthBytes) + "]，Hex为[" + directionFlagAndLengthHexString + "]");
        //上下行标记字节：使用上下行标记及长度的4个字节中的前2个字节，向右移位处理
        byte directionFlagByte = (byte) (directionFlagAndLengthIntValue >> 0x8);
        index += framePartLength;//帧上下行标记及长度判断通过，索引加上帧上下行标记及长度的长度

        //报文起始符，长度为1字节；在报文上下行标记及长度后有1个字节 0x02--表示报文起始符
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的报文起始符时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        //判断报文结束符是否正确
        byte packetsStartByte = inputBytes[index];
        if (packetsStartByte != Packets_Start_Byte)
            return returnParseError(result, "数据帧中的报文起始符错误，报文起始符应该为[" + RadixConvertUtil.toHexString(Packets_Start_Byte) +
                    "],实际报文中的起始符为[" + RadixConvertUtil.toHexString(packetsStartByte) + "]");
        index += framePartLength;//报文结束符判断通过，索引加上报文结束符的长度

        //上下行标记及长度解析：高4位用作上下行标识，其余12位表示报文正文长度
        //高4位上下行标识：0000表示上行，1000表示下行
        if ((directionFlagByte & 0x80) == 0x80)//0x80=1000 0000
            result.setUpDirection(false);
        else
            result.setUpDirection(true);
        //数据帧中的正文长度：2个字节，第1个字节中的高4为用作上下行标识(与0x0F后作为高位字节)
        byte contentLengthHighByte = (byte) (directionFlagByte & 0x0F);
        byte contentLengthLowByte = (byte) (directionFlagAndLengthIntValue & 0xFF);
        int contentLength = RadixConvertUtil.computeIntValue(RadixConvertUtil.computeIntValue(contentLengthHighByte), RadixConvertUtil.computeIntValue(contentLengthLowByte));
        result.setBodyLength(contentLength);
        //根据内容长度判断报文长度是否足够
        if (contentLength > 0x0) {
            logger.debug("报文中内容正文长度解析为：" + contentLength);
            //SL651协议中的长度是指报文正文的长度，不包含报文头、长度后面的报头、报文正文后的结束符和CRC
            frameMinTotalLength = index + contentLength;
            if (inputBytes.length < frameMinTotalLength)
                return returnParseError(result, "根据报文中的正文长度[" + contentLength + "]判断报文长度不足，报文总长度应至少为" + frameMinTotalLength);

            //根据报文正文长度获取正文字节数组
            byte[] userDataBytes = Arrays.copyOfRange(inputBytes, index, index + contentLength);
            String userDataAsciiString = RadixConvertUtil.toAsciiString(userDataBytes);
            //logger.debug("报文中内容正文Ascii为：" + userDataAsciiString);
            result.setBodyDataBytes(userDataBytes);
            result.setBodyDataString(userDataAsciiString);
            index += contentLength;//索引加上报文正文长度
        }

        //报文结束符，长度为1字节
        framePartLength = 0x1;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的结束符时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        //判断报文结束符是否正确
        byte packetsEndByte = inputBytes[index];
        if (packetsEndByte != Packets_End_Byte)
            return returnParseError(result, "数据帧中的报文结束符错误，报文结束符应该为[" + RadixConvertUtil.toHexString(Packets_End_Byte) +
                    "],实际报文中的结束符为[" + RadixConvertUtil.toHexString(packetsEndByte) + "]");
        index += framePartLength;//报文结束符判断通过，索引加上报文结束符的长度

        //CRC，长度为4字节
        framePartLength = 0x4;
        frameMinTotalLength = index + framePartLength;
        if (inputBytes.length < frameMinTotalLength)
            return returnParseError(result, "判断数据帧中的CRC校验码时报文长度不足，报文总长度应至少为" + frameMinTotalLength);
        byte[] crcBytes = Arrays.copyOfRange(inputBytes, index, index + framePartLength);
        result.setCrcBytes(crcBytes);
        //判断CRC是否正确
        String crcHexString = RadixConvertUtil.toAsciiString(crcBytes);
        int crcIntValue = RadixConvertUtil.hexStringToIntValue(crcHexString);
        if (crcIntValue < 0x0)
            return returnParseError(result, "数据帧中的CRC校验码错误，CRC校验码报文为[" + RadixConvertUtil.toHexString(crcBytes) + "]，Hex为[" + crcHexString + "]");
        byte[] calcCrcBytes = Arrays.copyOfRange(inputBytes, 0x0, index);//计算CRC输入的字节数组不包含crc的2个字节
        int calcCrcIntValue = CrcUtil.Crc16_Modbus(calcCrcBytes);
        //logger.debug("计算CRC，报文：" + RadixConvertUtil.toAsciiString(calcCrcBytes));
        //logger.debug("报文中CRC值为：" + crcIntValue + "，计算出的CRC值为：" + calcCrcIntValue);
        if (crcIntValue != calcCrcIntValue) {
            //计算出的crc与报文中的crc不一致
        }

        index += framePartLength;//报文CRC校验码判断通过，索引加上CRC校验码的长度

        //全部判断和验证均通过，解析成功；设置完整的数据帧字节数组
        result.setParseSuccess(true);
        byte[] frameBytes = Arrays.copyOfRange(inputBytes, 0x0, index);
        result.setParseFrameBytes(frameBytes);
        result.setParseFrameString(RadixConvertUtil.toAsciiString(frameBytes));

        //判断如果要解析正文中的数据进行解析
        if (parseBodyData == true && result.getBodyDataBytes() != null && result.getBodyDataBytes().length > 0x0) {
            //解析报文正文判断帧类型需要使用控制域和功能码，与解析正文方法中的顺序保持一致
            List<IotFrameBodyDataItem> bodyDataItems = parseBodyData(new byte[]{directionFlagByte}, result.getBodyDataBytes());
            if (bodyDataItems != null && bodyDataItems.size() > 0x0)
                result.setBodyDataItems(bodyDataItems);
        }

        logger.info("报文解析成功，正文长度[{}]，中心站地址[{}]，遥测站地址[{}]，密码[{}]，功能码[{}]，帧名称[{}]，帧类型[{}]，正文内容[{}]",
                result.getBodyLength(), result.getCenterAddress(), result.getStationAddress(), result.getPassword(),
                result.getFunctionCode(), result.getFrameName(), result.getFrameType(), result.getBodyDataString());

        return result;
    }

    /**
     * 返回数据帧解析错误结果
     *
     * @param result   返回解析结果对象
     * @param errorMsg 错误信息，返回结果中自动加上统一的前缀
     * @return qc.module.iot.protocol.IotFrameParseResult
     * @author QuCheng Tech
     * @since 2025/11/7
     */
    IotFrameParseResult returnParseError(IotFrameParseResult result, String errorMsg) {
        if (result == null) {
            result = new IotFrameParseResult();
            result.setParseSuccess(false);
        }

        result.setParseErrorMsg(parseErrorMessagePrefix + errorMsg);

        return result;
    }

    /**
     * 解析数据帧正文内容
     *
     * @param functionBytes  功能码字节数组，长度可以为1或＞1，长度＜1时无效不进行正文内容解析
     * @param functionBytes, bodyBytes 正文内容字节数组
     * @return java.util.List<qc.module.iot.protocol.IotFrameBodyDataItem>
     * @author QuCheng Tech
     * @since 2025/11/13
     */
    @Override
    public List<IotFrameBodyDataItem> parseBodyData(byte[] functionBytes, byte[] bodyBytes) {
        logger.debug("parseBodyData");
        //如果功能码或正文字节数组为空不能解析正文内容
        if (functionBytes != null && functionBytes.length > 0x0 &&
                bodyBytes != null && bodyBytes.length > 0x0) {
            byte directionFlagByte = functionBytes[0x0];//传入的第1字节为报文标识（报文标识及长度字节数组的第1个字节），用于判断上下行方向
            boolean isUpDirection = true;
            //上下行标记及长度解析：高4位用作上下行标识，其余12位表示报文正文长度
            //高4位上下行标识：0000表示上行，1000表示下行
            if ((directionFlagByte & 0x80) == 0x80)//0x80=1000 0000
                isUpDirection = false;
            List<IotFrameBodyDataItem> result = new LinkedList<>();
            //记录当前解析判断到的位置索引，数据帧的每个部分在解析前均要判断输入的字节数组总长是否足够
            int index = 0x0;
            //数据帧的每个结构框架部分的长度，判断到每个部分长度时进行赋值
            int framePartLength = 0x0;
            if (isUpDirection == true) {
                /* SL651 Ascii 上行报文正文基本结构
                 * 结构框架：流水号               发报时间       遥测站地址(地址标识符 遥测站地址) 遥测站分类码 观测时间(观测时间标识符 观测时间)           要素信息组(标识符 数据)
                 * 长度字节：  4                   12            2           10               1                  2         10                  2     n
                 * 解析举例：30334237  313830363232313633353030 5354 2030303030303030303133 204B              205454 2031383036323231363335 205A55203836372E333830 205654 2031342E323520
                 */
                //流水号，长度为4字节
                framePartLength = 0x4;
                byte[] serialNumberBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                String serialNumber = RadixConvertUtil.toAsciiString(serialNumberBytes);
                IotFrameBodyDataItem serialNumberDataItem = new IotFrameBodyDataItem();
                serialNumberDataItem.setBytes(serialNumberBytes);
                serialNumberDataItem.setHexString(RadixConvertUtil.toHexString(serialNumberBytes));
                serialNumberDataItem.setType(IotFrameBodyDataItemTypeEnum.STATUS);
                serialNumberDataItem.setCode("sn");
                serialNumberDataItem.setName("流水号");
                serialNumberDataItem.setValue(serialNumber);
                result.add(serialNumberDataItem);
                index += framePartLength;//索引加上流水号的长度

                //发报时间，长度为12字节
                framePartLength = 0xC;
                byte[] sendTimeBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                //logger.debug("解析报文内容，发报时间：" + RadixConvertUtil.toHexString(sendTimeBytes));
                LocalDateTime sendTime = this.parseTime(sendTimeBytes);
                if (sendTime != null) {
                    IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                    bodyDataItem.setBytes(sendTimeBytes);
                    bodyDataItem.setHexString(RadixConvertUtil.toHexString(sendTimeBytes));
                    bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.TIME_SEND);
                    bodyDataItem.setCode("sendtm");
                    bodyDataItem.setName("发报时间");
                    bodyDataItem.setValue(sendTime);
                    result.add(bodyDataItem);
                }
                index += framePartLength;//索引加上发报时间的长度

                //遥测站地址(地址标识符2字节 遥测站地址5字节)
                //地址标识符，长度为2字节
                framePartLength = 0x2;
                byte[] stationAddressPrefixBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                index += framePartLength;//索引加上遥测站地址引导符的长度
                index += 0x1;//遥测站地址引导符与遥测站地址之间还有0x20的分隔符，在此对位置索引+1进行跳过
                //遥测站地址，长度为10字节
                framePartLength = 0xA;
                byte[] stationAddressBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                String stationAddressAsciiString = RadixConvertUtil.toAsciiString(stationAddressBytes);
                IotFrameBodyDataItem stationAddressDataItem = new IotFrameBodyDataItem();
                stationAddressDataItem.setBytes(stationAddressBytes);
                stationAddressDataItem.setHexString(RadixConvertUtil.toHexString(stationAddressBytes));
                stationAddressDataItem.setType(IotFrameBodyDataItemTypeEnum.STATION_ADDRESS);
                stationAddressDataItem.setCode("stcd");
                stationAddressDataItem.setName("测站地址");
                stationAddressDataItem.setValue(stationAddressAsciiString);
                result.add(stationAddressDataItem);
                index += framePartLength;//索引加上遥测站地址的长度

                index += 0x1;//遥测站分类码前面还有0x20的分隔符，在此对位置索引+1进行跳过
                //遥测站分类码，长度为1字节；根据功能码定义选择
                framePartLength = 0x1;
                byte categoryCodeByte = bodyBytes[index];
                String categoryCodeString = RadixConvertUtil.toAsciiString(categoryCodeByte);
                SL651_Ascii_FrameStationCategoryCodeEnum stationCategoryCodeEnum = SL651_Ascii_FrameStationCategoryCodeEnum.getSL651_FrameStationCategoryCodeEnum(categoryCodeString);
                IotFrameBodyDataItem stationCategoryCodeDataItem = new IotFrameBodyDataItem();
                stationCategoryCodeDataItem.setBytes(new byte[]{categoryCodeByte});
                stationCategoryCodeDataItem.setHexString(RadixConvertUtil.toHexString(categoryCodeByte));
                stationCategoryCodeDataItem.setType(IotFrameBodyDataItemTypeEnum.STATION_CATEGORY);
                stationCategoryCodeDataItem.setCode("sttype");
                stationCategoryCodeDataItem.setName("测站分类");
                stationCategoryCodeDataItem.setValue(stationCategoryCodeEnum);
                result.add(stationCategoryCodeDataItem);
                index += framePartLength;//索引加上遥测站分类码的长度

                //观测时间(观测时间标识符 观测时间)
                index += 0x1;//观测时间标识符前面还有0x20的分隔符，在此对位置索引+1进行跳过
                //观测时间标识符，长度为2字节
                framePartLength = 0x2;
                byte[] dataTimePrefixBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                index += framePartLength;//索引加上观测时间标识符的长度
                index += 0x1;//观测时间前面还有0x20的分隔符，在此对位置索引+1进行跳过
                //观测时间，长度为10字节
                framePartLength = 0xA;
                byte[] dataTimeBytes = Arrays.copyOfRange(bodyBytes, index, index + framePartLength);
                //logger.debug("解析报文内容，观测时间：" + RadixConvertUtil.toHexString(dataTimeBytes));
                LocalDateTime dataTime = this.parseTime(dataTimeBytes);
                if (sendTime != null) {
                    IotFrameBodyDataItem bodyDataItem = new IotFrameBodyDataItem();
                    bodyDataItem.setBytes(dataTimeBytes);
                    bodyDataItem.setHexString(RadixConvertUtil.toHexString(dataTimeBytes));
                    bodyDataItem.setType(IotFrameBodyDataItemTypeEnum.TIME);
                    bodyDataItem.setCode("tm");
                    bodyDataItem.setName("时标");
                    bodyDataItem.setValue(dataTime);
                    result.add(bodyDataItem);
                }
                index += framePartLength;//索引加上遥测站地址的长度

                //logger.debug("解析报文内容，正文hex：" + RadixConvertUtil.toHexString(bodyBytes) + " 当前index=" + index);
                //再往后面的内容根据分隔符0x20(空格)进行分隔解析
                //205A55引导符 ZU
                //203836372E333830 	水位数值 867.380
                //205654引导符 VT
                //2031342E3235 	电压数值 14.25
                while (index <= bodyBytes.length - 0x6) {//一个要素至少需要6字节，2个分隔符、至少2字节的引导符、至少2字节的数据值
                    if (bodyBytes[index] == Content_Split_Byte) {
                        //如果当前字节是分隔符，获取下一个分隔符的位置索引
                        index++;//已经判断过的分隔符位置索引加1
                        int dataValueSplitByteIndex = -1;
                        //标识符/引导符可以是1个字符（1字节）或多个字符（多字节）
                        for (int i = index + 0x1; i < bodyBytes.length; i++) {
                            if (bodyBytes[i] == Content_Split_Byte) {
                                dataValueSplitByteIndex = i;
                                break;
                            }
                        }

                        if (dataValueSplitByteIndex < 0x0) {
                            //如果数据值的分隔符没有找到，当做无效；后面也不需要继续判断（没有后续的分隔符）
                            break;
                        } else {
                            //logger.debug("报文内容，引导符/标识符分隔index=" + index + " 数据值分隔index=" + dataValueSplitByteIndex);
                            //获取2个分隔符中间的引导符/标识符，起始索引跳过分隔符，截止索引不包含索引值位置的字节
                            byte[] elementDataValueIdentifierBytes = Arrays.copyOfRange(bodyBytes, index, dataValueSplitByteIndex);
                            index += elementDataValueIdentifierBytes.length;//索引加上引导符/标识符的长度字节
                            String elementDataValueIdentifierString = RadixConvertUtil.toAsciiString(elementDataValueIdentifierBytes);
                            SL651_IdentifierEnum identifierEnum = SL651_IdentifierEnum.getSL651_IdentifierEnum(elementDataValueIdentifierString);
                            //根据不同的标识符进行不同处理，不同的标识符对应的数据字节长度和转换系数不同 
                            int elementDataValueByteLength = 0x4;//要素数据值使用的字节数
                            if (identifierEnum.getIndex() >= SL651_IdentifierEnum.ELEMENT_RAIN_20.getIndex() &&
                                    identifierEnum.getIndex() <= SL651_IdentifierEnum.ELEMENT_RAIN_26.getIndex()) {
                                //降雨量，使用6字节
                                elementDataValueByteLength = 0x6;
                            } else if (identifierEnum == SL651_IdentifierEnum.ELEMENT_Q_27) {
                                //流量，使用12字节
                                elementDataValueByteLength = 0xC;
                            } else if (identifierEnum == SL651_IdentifierEnum.ELEMENT_V_38) {
                                //电压，使用5字节
                                elementDataValueByteLength = 0x5;
                            } else if (identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_39 ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3A ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3B ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3C ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3D ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3E ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_3F ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_40 ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_41 ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_42 ||
                                    identifierEnum == SL651_IdentifierEnum.ELEMENT_Z_43) {
                                //水位，使用7字节
                                elementDataValueByteLength = 0x7;
                            } else if (identifierEnum == SL651_IdentifierEnum.STATUS_45) {
                                //遥测站状态及报警信息，使用4字节
                                elementDataValueByteLength = 0x4;
                            }
                            //获取到数据值的分隔符后再往后获取下一个分隔符，如果2个分隔符之间的字节数量＜该标识符对应的字节数量，判断为没有对应的数据值
                            //实际报文如下：0683251110104012ST 0000000001 K TT 2511101040 Z1 1.650 Z M VT 11.45
                            //其中的Z和M标识符后面都没有数据值，需要进行跳过
                            int nextSplitByteIndex = -1;
                            for (int i = index + 0x1; i < index + elementDataValueByteLength; i++) {//如果在数据值的字节数组中包含有分隔符0x20（空格），判断是下一个标识符的开始
                                if (bodyBytes[i] == Content_Split_Byte) {
                                    nextSplitByteIndex = i;
                                    break;
                                }
                            }
                            if (nextSplitByteIndex >= 0x0) {
                                //数据帧字节中包含有分隔符0x20（空格），判断是下一个标识符的开始，此出获取数据值字节的截止设置为分隔符的位置索引
                                //logger.debug("报文内容，下一个引导符/标识符分隔index=" + nextSplitByteIndex + " 数据值分隔index=" + dataValueSplitByteIndex);
                                index = nextSplitByteIndex;
                            } else {
                                //数据值字节中不包含分隔符0x20（空格）
                                //获取数据帧的字节数组，起始索引加1跳过分隔符，截止索引加上数据值的字节长度
                                index += elementDataValueByteLength + 0x1;//索引加上数据值的结束位置索引（下一个分隔符）
                            }
                            byte[] elementDataValueBytes = Arrays.copyOfRange(bodyBytes, dataValueSplitByteIndex + 0x1, index);
                            //logger.debug("报文内容，引导符/标识符Hex：" + RadixConvertUtil.toHexString(elementDataValueIdentifierBytes) + " 标识符：" + RadixConvertUtil.toAsciiString(elementDataValueIdentifierBytes) +
                            //        "，数据值Hex:" + RadixConvertUtil.toHexString(elementDataValueBytes) + "，数据值：" + RadixConvertUtil.toAsciiString(elementDataValueBytes));
                            //先初始化一个数据值的项
                            IotFrameBodyDataItem elementDataValueItem = new IotFrameBodyDataItem();
                            elementDataValueItem.setBytes(elementDataValueBytes);
                            elementDataValueItem.setHexString(RadixConvertUtil.toHexString(elementDataValueBytes));
                            elementDataValueItem.setType(IotFrameBodyDataItemTypeEnum.VALUE);
                            elementDataValueItem.setName(identifierEnum.getName());
                            //如果没有对应的标识符枚举，code使用对应的byte值
                            if (identifierEnum == SL651_IdentifierEnum.UN_DEFINE) {
                                elementDataValueItem.setCode(RadixConvertUtil.toHexString(elementDataValueIdentifierBytes));
                                elementDataValueItem.setUnKnown(true);//未知的数据项
                            } else {
                                elementDataValueItem.setCode(identifierEnum.getName());
                            }
                            //判断数值字节数组，如果无数据不进行转换，设置解析结果值为横线（表示无数据）
                            if (this.isNoData(elementDataValueBytes) == true) {
                                elementDataValueItem.setValue(IotConstant.NO_DATA_VALUE_STRING);
                            } else {
                                //数据值不需要转换系数，直接将直接数组转换为对应的ascii码字符串，其中带有小数点
                                String elementDataValueString = RadixConvertUtil.toAsciiString(elementDataValueBytes);
                                try {
                                    double doubleValue = Double.parseDouble(elementDataValueString);
                                    elementDataValueItem.setValue(doubleValue);
                                } catch (Exception ex) {
                                    //转换时标时直接使用对应的ascii字符串
                                    elementDataValueItem.setValue(elementDataValueString);
                                }
                            }

                            result.add(elementDataValueItem);
                        }
                    } else {
                        index++;
                    }
                }
            } else {
                //下行报文，不解析内容
            }

            if (result.size() > 0x0)
                return result;
        }

        return null;
    }

    /**
     * 根据字节数组解析出时间，解析不出或者错误返回null
     *
     * @param bytes 字节数组
     * @return java.time.LocalDateTime
     * @author QuCheng Tech
     * @since 2025/11/14
     */
    LocalDateTime parseTime(byte[] bytes) {
        //传入字节数组错误或长度不够不进行解析
        if (bytes != null && bytes.length >= 0xA) {
            //hex:313830363232313633353030 	发报时间 180622163500
            //12个字节表示，如：180622163500  每个字节依次表示年、月、日、时、分、秒
            //10个字节表示，如：1806221635  每个字节依次表示年、月、日、时、分，
            //时标中均采用BCD码，每个数均使用高4位表示十位、低4位表示个位
            int year = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0x0], bytes[0x1]}, true);
            int month = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0x2], bytes[0x3]}, true);
            int day = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0x4], bytes[0x5]}, true);
            int hour = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0x6], bytes[0x7]}, true);
            int minute = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0x8], bytes[0x9]}, true);
            //秒默认为0，如果字节长度≥6，从字节数组中获取秒
            int second = 0x0;
            if (bytes.length >= 0xC)
                second = RadixConvertUtil.computeAsciiIntValue(new byte[]{bytes[0xA], bytes[0xB]}, true);

            //logger.debug("parseTime " + year + "/" + month + "/" + day + " " + hour + ":" + minute + ":" + second);

            LocalDateTime time = LocalDateTime.of(2000 + year, month, day, hour, minute, second);

            //logger.debug("parseTime hex:" + RadixConvertUtil.toHexString(bytes) + " 解析出的时间为：" + LocalDateTimeUtil.formatDateTime(time));

            return time;
        }

        return null;
    }

    /**
     * 判断指定的字节数组是否为无数据
     *
     * @param bytes 字节数组
     * @return boolean
     * @author QuCheng Tech
     * @since 2025/11/13
     */
    boolean isNoData(byte[] bytes) {
        if (bytes != null && bytes.length > 0x0) {
            for (int i = 0; i < bytes.length; i++) {
                if (bytes[i] != NO_VALUE_Byte)//任意一个字节不是无数据字节均判断为是有数据
                    return false;
            }
        }

        //如果传入的字节数组为空或者全部字节均判断为无数据
        return true;
    }

    /**
     * 生成回复报文，根据传入的报文解析结果判断，如果返回空表示不需要回复
     *
     * @param frameParseResult 收到的报文解析结果
     * @return byte[]
     * @author QuCheng Tech
     * @since 2025/11/8
     */
    @Override
    public byte[] generateReplyFrame(IotFrameParseResult frameParseResult) {
        logger.debug("generateReplyFrame");
        //如果解析结果为空或者解析错误不需要进行回复
        if (frameParseResult == null) {
            logger.debug("generateReplyFrame 传入的数据帧解析结果为null，无法生成回复报文");
        } else {
            if (frameParseResult.isParseSuccess() == false) {
                logger.debug("generateReplyFrame 传入的数据帧解析结果为解析失败，无法生成回复报文");
            } else {
                /* SL651 Hex 下行报文数据帧
                 * 结构框架：帧起始符 遥测站地址 中心站地址 密码 功能码 上下行标记及长度 报文起始符 报文正文 报文结束符 校验码
                 * 长度字节：   1       10       2      4    2        4           1        n        1     4
                 * 固定内容： 帧起始符-01、报文起始符-02、报文结束符-03
                 * 解析举例：
                 */

                //根据当前协议进行判断是否需要回复
                if (frameParseResult.getFrameType() == IotPacketsDataFrameTypeEnum.DATA) {
                    //当前协议中只有数据包收到后需要回复
                    //SL651 HEX协议回复需要根据功能码来进行回复和确定回复的报文正文内容，如果没有功能码或者功能码错误不进行回复
                    if (frameParseResult.getFunctionCodeBytes() == null || frameParseResult.getFunctionCodeBytes().length < 0x1) {
                        logger.debug("generateReplyFrame 传入的数据帧解析结果中功能码为空，无法生成回复报文");
                    } else {
                        //功能码使用1个字节，获取功能码的值
                        byte functionCodeByte = frameParseResult.getFunctionCodeBytes()[0x0];
                        //功能码解析，根据功能码对数据值类型进行赋值，确定是心跳还是数据帧
                        SL651_FrameFunctionCodeEnum functionCodeEnum = SL651_FrameFunctionCodeEnum.getSL651FrameFunctionCodeEnum(functionCodeByte);
                        if (functionCodeEnum.getIndex() >= SL651_FrameFunctionCodeEnum.REPORT_TEST.getIndex() &&
                                functionCodeEnum.getIndex() <= SL651_FrameFunctionCodeEnum.REPORT_PICTURE.getIndex()) {
                            //如果功能码是数据报送，生成对应回复报文
                            List<Byte> result = new LinkedList<>();
                            //帧起始符
                            result.add(Frame_Start_Byte);
                            //遥测站地址
                            for (int i = 0; i < frameParseResult.getStationAddressBytes().length; i++) {
                                result.add(frameParseResult.getStationAddressBytes()[i]);
                            }
                            //中心站地址
                            for (int i = 0; i < frameParseResult.getCenterAddressBytes().length; i++) {
                                result.add(frameParseResult.getCenterAddressBytes()[i]);
                            }
                            //密码
                            for (int i = 0; i < frameParseResult.getPasswordBytes().length; i++) {
                                result.add(frameParseResult.getPasswordBytes()[i]);
                            }
                            //功能码，响应使用的功能码与上行报文中的功能码一致
                            for (int i = 0; i < frameParseResult.getFunctionCodeBytes().length; i++) {
                                result.add(frameParseResult.getFunctionCodeBytes()[i]);
                            }
                            //上下行标记及长度：根据上行报文中的功能码确定回复的报文正文内容及长度
                            //上下行标记及长度解析：高4位用作上下行标识，其余12位表示报文正文长度;；高4位上下行标识：0000表示上行，1000表示下行(0x80=1000 0000)
                            result.add((byte) 0x80);//下行报文标识，如果报文正文长度超过0xFF需要在此字节的后4位进行累加
                            //需要获取解析收到数据值结果中的流水号和发报时间作为回复的正文内容；功能码为0x32的回复报文正文内容(长度为8字节)：收到的报文正文中的报文流水号(2字节)和发报时间YYMMddHHmmss 
                            int bodyContentLength = 0x08;//报文正文长度
                            result.add((byte) bodyContentLength);
                            //报文起始符
                            result.add(Packets_Start_Byte);
                            //报文正文：上报数据报文对应的下行报文正文均为流水号+发报时间
                            if (frameParseResult.getBodyDataBytes() != null && frameParseResult.getBodyDataBytes().length > 0x0) {
                                //使用上行报文中的流水号+发报时间
                                for (int i = 0; i < bodyContentLength; i++) {
                                    if (frameParseResult.getBodyDataBytes().length >= i)
                                        result.add(frameParseResult.getBodyDataBytes()[i]);
                                }
                            } else {
                                //上行报文正文内容为空，自动生成一个还是不回复？？
                            }
                            //报文结束符
                            result.add(Packets_End_Byte);
                            //CRC校验码
                            byte[] calcCrcBytes = RadixConvertUtil.toByteArray(result);
                            int calcCrcIntValue = CrcUtil.Crc16_Modbus(calcCrcBytes);
                            int[] calcCrcIntBytes = RadixConvertUtil.toByteArray(calcCrcIntValue, 0x2);//CRC为2个字节
                            logger.debug("计算CRC，报文：" + RadixConvertUtil.toHexString(calcCrcBytes));
                            logger.debug("计算出的CRC为：" + RadixConvertUtil.toHexString(calcCrcIntBytes) + "，计算出的CRC值为：" + calcCrcIntValue + "，计算出的CRC字符串为：" + String.format("%04X", calcCrcIntValue));
                            if (calcCrcIntBytes != null && calcCrcIntBytes.length > 0x0) {
                                for (int i = 0; i < calcCrcIntBytes.length; i++) {
                                    result.add((byte) (calcCrcIntBytes[i] & 0xFF));//每个字节与0xFF做与运算，确保取值范围在byte的范围内
                                }
                            }

                            //返回生成的回复报文
                            if (result.size() > 0x0) {
                                byte[] bytes = RadixConvertUtil.toByteArray(result);
                                logger.debug("generateReplyFrame 生成的回复报文为[" + RadixConvertUtil.toHexString(bytes) + "]");
                                return bytes;
                            }
                        } else {
                            logger.debug("generateReplyFrame 传入的数据帧解析结果中功能码为[" + functionCodeEnum + "]，判定为无需生成回复报文");
                        }
                    }
                } else {
                    logger.debug("generateReplyFrame 传入的数据帧解析结果中数据帧类型为[" + frameParseResult.getFrameType() + "]，判定为无需生成回复报文");
                }
            }
        }

        return null;
    }
}
